let sudoku = {};

sudoku.sudokuNums = new Array(9).fill(0).map(a => new Array(9).fill(0));
sudoku.sudokuObjs = new Array(9).fill(0).map(a => new Array(9).fill({ num: 0, isStable: false, expect: [], curCalc: false }));

sudoku.refreshSudoku = (count1, count2, count3, count4) => {
    $('#calc-result').text(`count1: ${count1 || 0}, count2: ${count2 || 0}, count3: ${count3 || 0}, count4: ${count4 || 0}`);

    let $container = $('#popup-container');
    $container.empty();

    let $rows = sudoku.sudokuObjs.map(a => {
        let $row = $('<div class="row"></div>');

        let $cols = a.map(b => {
            let val = b.num || '';

            let $item = $(`<div class="col">${val}</div>`);
            if (b.isStable) {
                $item.addClass('stable');
            } else if (b.expect?.length > 1) {
                $item.addClass('expect-multi');
                $item.text(b.expect?.length);
                $item.attr('title', b.expect.join(','));
            }

            if (b.curCalc) {
                $item.addClass('cur-calc');
            }
            if (b.curCalc5) {
                $item.addClass('cur-calc cur-calc-5');
            }
            return $item;
        });
        $row.append($cols);

        return $row;
    });

    $container.append($rows);

    let consoleRows = sudoku.sudokuObjs.map(a => a.map(b => (b.num || 0)).join(', ')).map(a =>`        [${a}]`).join(',\n');
    let str = `[
${consoleRows}
    ]`;
    console.warn(`refreshSudoku`, str);
};

sudoku.calc = () => {
    let count1 = 0;
    let count2 = 0;
    let count3 = 0;
    let count4 = 0;
    let count5 = 0;

    count1 = sudoku.calcExpectLeave1();
    if (count1 <= 0) {
        count2 = sudoku.calcExpectLeave2();
        
        if (count2 <= 0) {
            count3 = sudoku.calcExpectLeave3();

            if (count3 <= 0) {
                count4 = sudoku.calcExpectLeave4();

                if (count4 <= 0) {
                    count5 = sudoku.calcExpectLeave5();
                }
            }
        }
    }

    sudoku.refreshSudoku(count1, count2, count3, count4);
};

// 基础计算，根据当前元素的行、列和九宫格已存在的数字，计算值
sudoku.calcExpectLeave1 = () => {
    let count = 0;

    sudoku.sudokuObjs.forEach((row, rowindex) => {
        row.forEach((col, colindex) => {
            col.curCalc = false;
            col.curCalc5 = false;

            if (col.isStable) {
                return;
            }
            if (col.num) {
                return;
            }

            let exists = sudoku.getExistNums(rowindex, colindex);
            let expect = [];

            for (let i = 1; i <= 9; i++) {
                if (!exists.includes(i)) {
                    expect.push(i);
                }
            }

            if (expect.length === 1) {
                col.expect = [];
                col.num = expect[0];
                col.curCalc = true;
                count++;
            } else if (col.expect?.length === 0 || expect.length < col.expect?.length) {
                col.expect = expect;
                col.num = '';
            } else {
                // pass
                console.warn(`calc 1 expect skip, row:${rowindex}, col: ${colindex}, expect: ${col.expect.join(',')}`);
            }

        });
    });

    return count;
};

sudoku.getExistNums = (rowindex, colindex) => {
    let rowExists = [];
    let colExists = [];
    let blockExists = [];

    sudoku.sudokuObjs[rowindex].forEach(a => {
        if (a.num) {
            rowExists.push(a.num);
        }
    });

    sudoku.sudokuObjs.forEach(a => {
        if (a[colindex].num) {
            colExists.push(a[colindex].num);
        }
    });

    let rowStart = Math.floor(rowindex / 3) * 3;
    let colStart = Math.floor(colindex / 3) * 3;
    for (let i = rowStart; i < rowStart + 3; i++) {
        for (let j = colStart; j < colStart + 3; j++) {
            if (sudoku.sudokuObjs[i][j].num) {
                blockExists.push(sudoku.sudokuObjs[i][j].num);
            }
        }
    }

    // distinct
    let total = rowExists.concat(colExists).concat(blockExists);
    let res = Array.from(new Set(total))
    return res;
};

sudoku.getExistExpectNums = (rowindex, colindex) => {
    let rowExists = [];
    let colExists = [];
    let blockExists = [];

    sudoku.sudokuObjs[rowindex].forEach((a, i) => {
        if (i === colindex) {
            return
        }

        if (a.num) {
            rowExists.push(a.num);
        } else if (a.expect.length > 0) {
            rowExists.push(...a.expect);
        }
    });

    sudoku.sudokuObjs.forEach((a, i) => {
        if (i === rowindex) {
            return;
        }

        if (a[colindex].num) {
            colExists.push(a[colindex].num);
        } else if (a[colindex].expect.length > 0) {
            colExists.push(...a[colindex].expect);
        }
    });

    let rowStart = Math.floor(rowindex / 3) * 3;
    let colStart = Math.floor(colindex / 3) * 3;
    for (let i = rowStart; i < rowStart + 3; i++) {
        for (let j = colStart; j < colStart + 3; j++) {
            if (rowindex === i && colindex === j) {
                continue;
            }

            if (sudoku.sudokuObjs[i][j].num) {
                blockExists.push(sudoku.sudokuObjs[i][j].num);
            } else if (sudoku.sudokuObjs[i][j].expect.length > 0) {
                blockExists.push(...sudoku.sudokuObjs[i][j].expect);
            }
        }
    }

    let arr = [
        Array.from(new Set(rowExists)),
        Array.from(new Set(colExists)),
        Array.from(new Set(blockExists)),
    ];

    let res = sudoku.getCommonElements(arr);
    return res;
}

sudoku.getCommonElements = (arrays) => {
    return arrays[0].filter(item =>
        arrays.every(arr => arr.includes(item))
    );
}

// 计算出自身元素外的预期值，如果是8个，则可以计算出自身的值
sudoku.calcExpectLeave2 = () => {
    let count = 0;

    sudoku.sudokuObjs.forEach((row, rowindex) => {
        row.forEach((col, colindex) => {
            if (col.isStable) {
                return;
            }
            if (col.num) {
                return;
            }

            let exists = [];

            if (rowindex === 2 && colindex === 4) {
                exists = sudoku.getExistExpectNums(rowindex, colindex);
            } else {
                exists = sudoku.getExistExpectNums(rowindex, colindex);
            }

            if (exists.length === 8) {
                for (let i = 1; i <= 9; i++) {
                    if (!exists.includes(i)) {
                        col.expect = [];
                        col.num = i;
                        col.curCalc = true;
                        count++;
                    }
                }
            }
        });
    });

    return count;
}

// 减少自身的预期值
sudoku.calcExpectLeave3 = () => {
    let count = 0;

    sudoku.sudokuObjs.forEach((row, rowindex) => {
        row.forEach((col, colindex) => {
            if (col.isStable) {
                return;
            }
            if (col.num) {
                return;
            }

            let exists = [];

            if (rowindex === 2 && colindex === 4) {
                exists = sudoku.getExistExpectNumGroups(rowindex, colindex);
            } else {
                exists = sudoku.getExistExpectNumGroups(rowindex, colindex);
            }

            let expect = col.expect;
            exists.forEach(arr => {
                expect = expect.filter(a => !arr.includes(a));
            });

            if (expect.length === 1) {
                col.expect = [];
                col.num = expect[0];
                col.curCalc = true;
                count++;
            }
        });
    });

    return count;
}

// 获取Exists Expect Groups
sudoku.getExistExpectNumGroups = (rowindex, colindex) => {
    let rowExistExpects = [];
    let colExistExpects = [];
    let blockExistExpects = [];

    sudoku.sudokuObjs[rowindex].forEach((a, i) => {
        if (i === colindex) {
            return
        }

        if (a.num) {
            return;
        }

        rowExistExpects.push(a.expect);
    });

    sudoku.sudokuObjs.forEach((a, i) => {
        if (i === rowindex) {
            return;
        }

        if (a.num) {
            return;
        }

        if (a[colindex].expect.length > 0) {
            colExistExpects.push(a[colindex].expect);
        }
    });

    let rowStart = Math.floor(rowindex / 3) * 3;
    let colStart = Math.floor(colindex / 3) * 3;
    for (let i = rowStart; i < rowStart + 3; i++) {
        for (let j = colStart; j < colStart + 3; j++) {
            if (rowindex === i && colindex === j) {
                continue;
            }

            if (sudoku.sudokuObjs[i][j].num) {
                continue;
            }

            if (sudoku.sudokuObjs[i][j].expect.length > 0) {
                blockExistExpects.push(sudoku.sudokuObjs[i][j].expect);
            }
        }
    }

    let arr = [
        ...getExistExpectNumGroupItem(rowExistExpects),
        ...getExistExpectNumGroupItem(colExistExpects),
        ...getExistExpectNumGroupItem(blockExistExpects)
    ];

    let strArr = arr.map(a => a.join('_'));
    let distinctArr = Array.from(new Set(strArr)).map(a=>a.split('_').map(b=>parseInt(b)));

    return distinctArr;
}

function getExistExpectNumGroupItem(existExpects) {
    let strArr = existExpects.map(a => a.join('_'));
    let groupMap = {};

    strArr.forEach(a => {
        groupMap[a] = 0;
    });

    let keys = Object.keys(groupMap);

    keys.forEach(a => {
        let aArr = a.split('_');

        strArr.forEach(b => {
            let bArr = b.split('_'); 
            if (bArr.every(item => aArr.includes(item))) {
                groupMap[a]++;
            }
        });
    });


    let groups = [];

    keys.forEach(a => {
        let arr = a.split('_');
        if (arr.length === groupMap[a]) {
            groups.push(arr);
        }
    });

    return groups;
}

// Exclude self own expected group according to the expected rows of other subgrids
// 自身的预期值可以通过其他九宫格中预期的值进行排查，按行或按列
sudoku.calcExpectLeave4 = () => {
    let count = 0;

    sudoku.sudokuObjs.forEach((row, rowindex) => {
        row.forEach((col, colindex) => {
            if (col.isStable) {
                return;
            }
            if (col.num) {
                return;
            }

            let exists = [];

            if (rowindex === 3 && colindex === 7) {
                exists = getExistsNumFormRowOrCol(rowindex, colindex);
            } else {
                exists = getExistsNumFormRowOrCol(rowindex, colindex);
            }

            let preStr = col.expect.join(',');
            col.expect = col.expect.filter(a => !exists.includes(a));
            let nowStr = col.expect.join(',');

            if (preStr !== nowStr) {
                console.warn(`calcExpectLeave4 work, rowindex: ${rowindex}, colindex: ${colindex}, preStr: ${preStr}, nowStr: ${nowStr}`);
            }
            
            if (col.expect.length === 1) {
                col.expect = [];
                col.num = col.expect[0];
                col.curCalc = true;
                count++;
            }
        });
    });

    return count;
}

function getExistsNumFormRowOrCol(rowindex, colindex) {
    let existsNum = [];

    let subGridRow = Math.floor(rowindex / 3);
    let subGridCol = Math.floor(colindex / 3);

    for (let subGridRowI = 0; subGridRowI < 3; subGridRowI++) {
        if (subGridRow === subGridRowI) {
            continue;
        }

        let arr = getExistsNumFormRow(subGridRowI, subGridCol, rowindex, colindex);
        existsNum.push(...arr);
    }

    for (let subGridColI = 0; subGridColI < 3; subGridColI++) {
        if (subGridCol === subGridColI) {
            continue;
        }

        let arr = getExistsNumFormCol(subGridRow, subGridColI, rowindex, colindex);
        existsNum.push(...arr);
    }

    let res = distinctArr(existsNum);
    return res;
}

function getExistsNumFormRow(subGridRow, subGridCol, rowindex, colindex) {
    let rowStart = subGridRow * 3;
    let colStart = subGridCol * 3;
    
    let expectGroup = [];
    let expectGroupOther = [];

    for (let i = rowStart; i < rowStart + 3; i++) {
        for (let j = colStart; j < colStart + 3; j++) {
            let item = sudoku.sudokuObjs[i][j];

            if (item.num) {
                continue;
            }

            if (item.expect.length <= 0) {
                continue;
            }

            if (j === colindex) {
                expectGroup.push(...item.expect);
            } else {
                expectGroupOther.push(...item.expect);
            }
        }
    }

    expectGroup = distinctArr(expectGroup);
    expectGroupOther = distinctArr(expectGroupOther);

    let existsNum = expectGroup.filter(a => !expectGroupOther.includes(a) );
    return existsNum;
}

function distinctArr(arr) {
    // distinct arr item
    let res = Array.from(new Set(arr));
    return res;
}

function getExistsNumFormCol(subGridRow, subGridCol, rowindex, colindex) {
    let rowStart = subGridRow * 3;
    let colStart = subGridCol * 3;

    let expectGroup = [];
    let expectGroupOther = [];

    for (let i = rowStart; i < rowStart + 3; i++) {
        for (let j = colStart; j < colStart + 3; j++) {
            let item = sudoku.sudokuObjs[i][j];

            if (item.num) {
                continue;
            }

            if (item.expect.length <= 0) {
                continue;
            }

            if (i === rowindex) {
                expectGroup.push(...item.expect);
            } else {
                expectGroupOther.push(...item.expect);
            }
        }
    }

    expectGroup = distinctArr(expectGroup);
    expectGroupOther = distinctArr(expectGroupOther);

    let existsNum = expectGroup.filter(a => !expectGroupOther.includes(a));
    return existsNum;
}

// group by same expect
// 根据预期值分组，移除掉自身多余的预期值。
sudoku.calcExpectLeave5 = () => {
    let count = 0;

    for (let subGridRow = 0; subGridRow < 3; subGridRow++) {
        for (let subGridCol = 0; subGridCol < 3; subGridCol++) {

            if (subGridRow === 1 && subGridCol === 2) {
                let temps = 'this is for debug point';
            }

            // get sub grid expect nums
            let groups = [];
            for (let i = subGridRow * 3; i < subGridRow * 3 + 3; i++) {
                for (let j = subGridCol * 3; j < subGridCol * 3 + 3; j++) {
                    let item = sudoku.sudokuObjs[i][j];

                    if (item.expect.length > 0) {
                        groups.push(item.expect);
                    }
                }
            }
            let groupNums = getGroupNums(groups);

            // set expect 
            for (let i = subGridRow * 3; i < subGridRow * 3 + 3; i++) {
                for (let j = subGridCol * 3; j < subGridCol * 3 + 3; j++) {
                    let item = sudoku.sudokuObjs[i][j];

                    if (item.expect.length <= 0) {
                        continue;
                    }

                    let resExpect = getShortExpectNums(item.expect, groupNums);

                    if (resExpect.length === item.expect.length) {
                        continue;
                    }

                    console.warn(`calc 5 works, row: ${i}, col: ${j}. pre: ${item.expect.join(',')}   now: ${resExpect.join(',')} `);
                    

                    if (resExpect.length === 1) {
                        item.expect = [];
                        item.num = col.expect[0];
                        item.curCalc = true;
                        count++;
                    } else {
                        item.expect = resExpect;
                        item.curCalc5 = true;
                    }
                }
            }
        }
    }

    return count;
}

function getGroupNums(groups) {
    // let groups = [[2, 3, 5, 7, 8], [2, 3], [2, 4, 5, 9], [2, 4, 5, 9], [2, 4, 9], [2, 4, 7, 8], [2, 3, 4]];

    let numCountMap = {};

    groups.forEach(a => a.forEach(b => {
        numCountMap[b] = numCountMap[b] || 0;
        numCountMap[b]++;
    }));

    let tempGroups = [];

    groups.forEach(a => {
        let item = [];
        let minLength = 999;

        a.forEach(b => {
            minLength = Math.min(minLength, numCountMap[b]);
        });

        a.forEach(b => {
            if (numCountMap[b] === minLength) {
                item.push(b)
            }
        });

        if (item.length === minLength) {
            tempGroups.push(item);
        }
    });

    let arr = tempGroups.map(a => a.join(','));

    let matchMap = {};
    arr.forEach(a => {
        matchMap[a] = matchMap[a] || 0;
        matchMap[a]++;
    });

    let keys = Object.keys(matchMap);
    let resultGroups = [];

    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        let keyNums = key.split(',').map(a=>parseInt(a));
        if (matchMap[key] === key.split(',').length) {
            resultGroups.push(keyNums);
        }
    }

    return resultGroups;
}

function getShortExpectNums(expectNums, groupNums) {
    for (const element of expectNums) {
        let matchGroupNum = groupNums.find(a => a.includes(element));
        if (matchGroupNum) {
            // clone array
            return matchGroupNum.join(',').split(',').map(a=>parseInt(a));
        }
    }

    return expectNums;
}